﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using DotNetX;
using RPGXEngine;

namespace RPGXEngine
{
    public class ScriptParser
    {
        #region Properties

        #endregion

        #region Public Methods

        public ScriptDataTree Parse(string script, ScriptManager sm)
        {
            ScriptDataTree tree = new ScriptDataTree();

            this.ParseFunctions(script, tree, sm);

            return tree;
        }

        public void ProcessFunctionBodyParts(ScriptManager sm)
        {
            foreach (var f in sm.DataTree.Functions)
            {
                for (int i = 0; i < f.BodyParts.Count; i++)
                {
                    var c = f.BodyParts[i];
                    ICommand cmd = this.ProcessCommand(c, f, sm);

                    if (cmd.IsIf) // do a similar thing with loops
                    {
                        this.ParseIf(cmd, ref i, f, sm);
                    }
                }
            }
        }

        #endregion

        #region Private Methods

        private void ParseIf(ICommand cmd, ref int i, Function f, ScriptManager sm)
        {
            string c = String.Empty;

            i++;

            Command_If theIf = cmd as Command_If;
            if (theIf == null)
                return;

            Command_If.IfPart part = new Command_If.IfPart();
            theIf.IfParts.Add(part);

            // the command factory adds the condition as a child part of the if
            part.Condition = theIf.ChildrenCommands[0];
            theIf.ChildrenCommands.Clear();

            bool isInBody = false;

            for (; i < f.BodyParts.Count; i++)
            {
                c = f.BodyParts[i].Trim();

                if (c == "{")
                {
                    isInBody = true;
                    continue;
                }
                else if (c == "}")
                {
                    isInBody = false;

                    // check to see if there is an else
                    if (i < f.BodyParts.Count - 1)
                    {
                        c = f.BodyParts[i + 1];
                        ICommand elseCmd = CommandFactory.Create(c, sm);

                        if (elseCmd.IsIf && ((Command_If)elseCmd).IsElse)
                        {
                            continue;
                        }
                        else
                        {
                            break;
                        }
                    }
                }
                else
                {
                    ICommand ifCmd = CommandFactory.Create(c, sm);

                    if (ifCmd.IsIf && ((Command_If)ifCmd).IsElse)
                    {
                        var newPart = new Command_If.IfPart();
                        theIf.IfParts.Add(newPart);

                        part = newPart;

                        // the command factory adds the condition as a child part of the if
                        if (ifCmd.ChildrenCommands.Count > 0)
                        {
                            part.Condition = ifCmd.ChildrenCommands[0];
                            ifCmd.ChildrenCommands.Clear();
                        }
                    }
                    else if (ifCmd.IsIf && isInBody)
                    {
                        this.ParseIf(ifCmd, ref i, f, sm);
                        part.Commands.Add(ifCmd);
                    }
                    else
                    {
                        part.Commands.Add(ifCmd);
                    }
                }
            }
        }

        private void ParseFunctions(string script, ScriptDataTree tree, ScriptManager sm)
        {
            List<string> functions = this.ReadFunctions(script);

            List<Function> processedFunctions = this.ProcessFunctions(functions, sm);
            foreach (var f in processedFunctions)
                tree.Functions.Add(f);
        }

        private List<Function> ProcessFunctions(List<string> functions, ScriptManager sm)
        {
            List<Function> funcs = new List<Function>();

            foreach(var f in functions)
            {
                Function func = this.ProcessFunction(f, sm);

                if (func != null)
                    funcs.Add(func);
            }

            return funcs;
        }

        private Function ProcessFunction(string f, ScriptManager sm)
        {
            Function function = new Function();

            StringReader reader = new StringReader(f);

            string signiture = reader.ReadLine();

            this.ProcessFunctionSigniture(signiture, function);

            reader.ReadLine(); // {

            while (true)
            {
                string command = reader.ReadLine();
                if (String.IsNullOrEmpty(command))
                    break;

                command = command.Trim();

                function.BodyParts.Add(command);
            }

            // ignore the final }

            reader.Close();

            return function;
        }

        private ICommand ProcessCommand(string command, Function function, ScriptManager sm)
        {
            ICommand cmd = CommandFactory.Create(command, sm);
            function.Commands.Add(cmd);

            return cmd;
        }

        private void ProcessFunctionSigniture(string signiture, Function function)
        {
            StringReader reader = new StringReader(signiture);

            string dataType = reader.ReadWord();

            function.ReturnType = this.ToDataType(dataType);

            string methodName = reader.ReadUntilCharacter('(');
            function.Name = methodName.Trim();

            reader.Read(); // the '('

            string args = String.Empty;
            while (true)
            {
                char c = (char)reader.Read();
                if (c == ')')
                    break;

                args += c;
            }

            StringReader r = new StringReader(args);
            while (true)
            {
                string dt = r.ReadWord();
                r.Read();
                if (String.IsNullOrEmpty(dt))
                    break;

                string name = r.ReadWord();
                if (String.IsNullOrEmpty(name))
                    break;

                IVariableType var = VariableTypeFactory.Create(this.ToDataType(dt),
                    name);

                function.Arguments.Add(var);

                char c = (char)r.Peek();
                if (c != ',' && !Char.IsWhiteSpace(c))
                    break;

                r.Read(); // the ,
            }
            r.Close();

            reader.Close();
        }

        private DataType ToDataType(string dataType)
        {
            DataType dt = DataType.Var;

            try
            {
                dt = (DataType)Enum.Parse(typeof(DataType), dataType, true);
            }
            catch (Exception)
            {
                // TODO: emmit compiler error here...
            }

            return dt;
        }

        private enum ReadState
        {
            FunctionSigniture,
            OpeningBrace,
            Body,
            ClosingBrace,
        }

        private List<string> ReadFunctions(string script)
        {
            // i am going to assume that the script has no errors for the time being...
            StringReader reader = new StringReader(script);

            List<string> functions = new List<string>();
            string currentFunction = String.Empty;

            ReadState readState = ReadState.FunctionSigniture;

            string line = String.Empty;
            while((line = reader.ReadLine()) != null)
            {
                line = line.Trim();

                if (String.IsNullOrEmpty(line))
                    continue;

                if (readState == ReadState.FunctionSigniture)
                {
                    currentFunction = String.Format("{0}{1}\n",
                        currentFunction, line);

                    readState = ReadState.Body;

                    currentFunction = String.Format("{0}{1}\n",
                        currentFunction,
                        this.ReadBlock(reader, ref readState));

                    functions.Add(currentFunction);

                    currentFunction = String.Empty;

                    readState = ReadState.FunctionSigniture;
                }
            }

            reader.Close();

            return functions;
        }

        private string ReadBlock(StringReader reader, ref ReadState readState)
        {
            string block = String.Empty;

            int depth = 0;
            string line = String.Empty;

            while (true)
            {
                line = reader.ReadLine().Trim(); // code must not be on the same line as an opening or closing brace
                if (String.IsNullOrEmpty(line))
                    continue;

                if (line == "{")
                {
                    depth++;
                }
                else if (line == "}")
                {
                    depth--;
                    if (depth <= 0)
                        break;
                }

                block = String.Format("{0}{1}\n",
                    block, line);
            }

            return block;
        }

        #endregion
    }
}
